<?php

namespace Drupal\jsonapi_cross_bundles\Context;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\jsonapi\Context\FieldResolver;
use Drupal\jsonapi\ResourceType\ResourceType;
use Drupal\jsonapi_cross_bundles\ResourceType\CrossBundlesResourceType;

/**
 * Decorated field resolver to support cross-bundle resource types.
 */
class CrossBundleFieldResolver extends FieldResolver {

  /**
   * Get all item definitions from a set of resources types by a field name.
   *
   * Overridden to support cross bundle resource types.
   *
   * @param \Drupal\jsonapi\ResourceType\ResourceType[] $resource_types
   *   The resource types on which the field might exist.
   * @param string $field_name
   *   The field for which to retrieve field item definitions.
   *
   * @return \Drupal\Core\TypedData\ComplexDataDefinitionInterface[]
   *   The found field item definitions.
   */
  protected function getFieldItemDefinitions(array $resource_types, $field_name) {
    list($bundle_specific, $cross_bundle) = array_reduce($resource_types, function ($separated, ResourceType $resource_type) {
      $separated[(int) ($resource_type instanceof CrossBundlesResourceType)][] = $resource_type;
      return $separated;
    }, [0 => [], 1 => []]);
    $cross_bundle_candidates = array_reduce($cross_bundle, function (array $candidates, CrossBundlesResourceType $resource_type) use ($field_name) {
      $per_bundle_candidates = parent::getFieldItemDefinitions($resource_type->getBundleResourceTypes(), $field_name);
      // Field names are unique across bundles of an entity type and since a
      // cross bundle resource type can only exist for a single resource type,
      // it's acceptable to take the first candidate found—they're all the same.
      return array_merge($candidates, !empty($per_bundle_candidates)
        ? [$resource_type->getTypeName() => array_shift($per_bundle_candidates)]
        : []
      );
    }, []);
    return array_merge(parent::getFieldItemDefinitions($bundle_specific, $field_name), $cross_bundle_candidates);
  }

  /**
   * Gets the field access result for the 'view' operation.
   *
   * Overridden to support cross bundles.
   *
   * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
   *   The JSON:API resource type on which the field exists.
   * @param string $internal_field_name
   *   The field name for which access should be checked.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   The 'view' access result.
   */
  protected function getFieldAccess(ResourceType $resource_type, $internal_field_name) {
    if ($resource_type instanceof CrossBundlesResourceType) {
      return array_reduce($resource_type->getBundleResourceTypes(), function (AccessResult $previous, ResourceType $resource_type) use ($internal_field_name) {
        return $resource_type->hasField($internal_field_name)
          ? $previous->andIf($this->getFieldAccess($resource_type, $internal_field_name))
          : $previous;
      }, AccessResult::allowed());
    }
    return parent::getFieldAccess($resource_type, $internal_field_name);
  }

  /**
   * filter支持type与vid的原值查询
   */
  public function resolveInternalEntityQueryPath($entity_type_id, $bundle, $external_field_name) {
    if($external_field_name == 'drupal_internal__type') {
      return 'type';
    }
    if($entity_type_id == 'taxonomy_term' && $external_field_name == 'drupal_internal__vid') {
      return 'vid';
    }
    return parent::resolveInternalEntityQueryPath($entity_type_id, $bundle, $external_field_name);
  }

}
